package com.bingbaihanji.core.config.security;

import com.alibaba.fastjson2.JSON;
import com.bingbaihanji.core.config.security.jwt.JwtAuthenticationTokenFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig {

    public static final String APPLICATION_JSON_UTF8_VALUE = "application/json;charset=UTF-8";

    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;  // 处理权限不足的自定义处理器

    @Autowired
    private MyAuthenticationEntryPoint myAuthenticationEntryPoint;  // 处理未认证请求的自定义处理器

    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;  // 处理认证成功的自定义处理器

    /**
     * 创建自定义的 JWT 令牌过滤器 Bean。
     *
     * @return JwtAuthenticationTokenFilter 实例
     */
    @Bean
    public JwtAuthenticationTokenFilter getOncePerRequestFilter() {
        return new JwtAuthenticationTokenFilter();
    }

    /**
     * 创建密码加密器 Bean。
     *
     * @return PasswordEncoder 实例，使用 BCrypt 进行密码加密
     */
    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置 Spring Security 的过滤链。
     *
     * @param http HttpSecurity 对象，用于配置安全策略
     * @return SecurityFilterChain 实例
     * @throws Exception 可能抛出的异常
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        // 配置不需要认证的 URL
        String[] permitAll = new String[]{
                "/css/**", "/js/**",
                "/webjars/**", "/static/**",
                "/layui/**",
                "/layui/css/**",
                "/layui/css/modules/laydate/default/**",
                "/layui/css/modules/layer/default/**",
                "/layui/font/**",
                "/layui/images/face/**",
                "/layui/lay/modules/**",
                "/doc.html/**",
                "/login"
        };

        // 配置 HTTP 安全策略
        http.authorizeHttpRequests((requests) -> requests
                // 允许访问不需要认证的 URL
                .requestMatchers(permitAll).permitAll()

                // 基于角色的权限控制
                .requestMatchers("/user/**").hasRole("ADMIN")

                // 对所有其他请求进行认证
                .anyRequest().authenticated()
        );

        // 配置跨域支持
        http.cors(Customizer.withDefaults());

        // 禁用 CSRF 保护
        http.csrf(AbstractHttpConfigurer::disable);

        // 配置会话管理
        http.sessionManagement(session -> session
                .maximumSessions(1)  // 限制同一时间内的最大会话数
                .expiredSessionStrategy(event -> {
                    // 创建响应结果对象
                    Map<String, Object> result = new HashMap<>();
                    result.put("code", -1);
                    result.put("message", "该账号已从其他设备登录");

                    // 将结果对象转换为 JSON 字符串
                    String json = JSON.toJSONString(result);

                    HttpServletResponse response = event.getResponse();
                    // 设置响应内容类型，并返回 JSON 响应
                    response.setContentType(APPLICATION_JSON_UTF8_VALUE);
                    response.getWriter().println(json);
                })
        );

        // 配置表单登录
        http.formLogin(form -> form
                .loginPage("/login").permitAll()  // 配置登录页面，并允许所有用户访问
                .usernameParameter("username")  // 配置自定义的用户名参数
                .passwordParameter("password")  // 配置自定义的密码参数
                .failureUrl("/login?error")  // 登录失败时的重定向 URL
                .loginProcessingUrl("/login")  // 自定义登录请求 URL
                .successHandler(myAuthenticationSuccessHandler)  // 自定义认证成功处理器
                .failureHandler(myAuthenticationEntryPoint)  // 自定义认证失败处理器
        );

        // 配置异常处理
        http.exceptionHandling(exception -> {
                    exception.accessDeniedHandler(myAccessDeniedHandler);  // 处理权限不足的异常
                    exception.authenticationEntryPoint((HttpServletRequest request,
                                                        HttpServletResponse response,
                                                        AuthenticationException authException) -> {
                        // 创建响应结果对象
                        Map<String, Object> result = new HashMap<>();
                        result.put("code", -1);
                        result.put("message", "请在登录后访问");

                        // 将结果对象转换为 JSON 字符串
                        String json = JSON.toJSONString(result);

                        // 设置响应内容类型，并返回 JSON 响应
                        response.setContentType(APPLICATION_JSON_UTF8_VALUE);
                        response.getWriter().println(json);
                    });
                }
        );

        // 添加 JWT 过滤器，放在 UsernamePasswordAuthenticationFilter 之前
        http.addFilterBefore(getOncePerRequestFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    WebSecurityCustomizer webSecurityCustomizer() {
        // 单独处理一下 h2-console 如果放在 WHITE_LIST_URLS 中将不起作用 原因是上面的过滤走的是 MvcRequestMatcher 匹配器
        // 而 h2-console 不属于 DispatcherServlet 的一部分
        return web -> web.ignoring()
                .requestMatchers(new AntPathRequestMatcher("/h2-console/**"));
    }
}
